/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Implementation for "Delayed Link" extension of the AP.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "delayedlink.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "ownutils.h"
#include "owndebug.h"

//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
static void delayedLinkPrintFrameInfoEntry(const int level, 
					   const TDelayedFrameInfo *aFrameInfoEntry);
//*****************************************************************************
//*****************************************************************************

TDelayedLinkInfo *delayedLinkCreateLinkInfoEntry(const int aDelay_ms)
{
  // Allocate memory for new LinkInfoEntry
  TDelayedLinkInfo *nentry = (TDelayedLinkInfo *)malloc(sizeof(TDelayedLinkInfo));
  assert(nentry != NULL);

  // Zero out the memory region
  memset(nentry, 0, sizeof(TDelayedLinkInfo));

  // Assign initial delay
  nentry->linkDelayMs = aDelay_ms;

  // Resize frameInfoArray to 0 elements (Will allocate memory according to policy)
  bufferInit(&nentry->frameInfoArray);

  // Return the newly created entry
  return nentry;
}
//*****************************************************************************

void delayedLinkDestroyLinkInfoEntry(TDelayedLinkInfo *aLinkInfoEntry)
{
  // First, destroy memory allocated for entries.
  bufferDeinit(&aLinkInfoEntry->frameInfoArray);
  
  // Then, destroy the entry itself
  free(aLinkInfoEntry);
  aLinkInfoEntry = NULL;
}
//*****************************************************************************

int delayedLinkPushFrame(TDelayedLinkInfo *aInfo, const int aSize)
{
  int result = 0;

  assert(aInfo->frameInfoArraySize >= 0);


  // New entry
  TDelayedFrameInfo fentry;

  // Store datasize
  fentry.data_size = aSize;

  // Store current timestamp
  ownTime_GetTimeOfDay(&fentry.received_time_s, &fentry.received_time_us);
  
  // Increment arraysize 
  ++aInfo->frameInfoArraySize;
  
  // Store the new entry to array.
  bufferAppend(&aInfo->frameInfoArray, (unsigned char*)&fentry, sizeof(TDelayedFrameInfo));

  // return result. Currently no errors can happen
  return result;
}
//*****************************************************************************

int delayedLinkPopFrame(TDelayedLinkInfo *aInfo)
{
  int result = -1;

  assert(aInfo->frameInfoArraySize >= 0);
  
  // If array has elements > 0
  if (aInfo->frameInfoArraySize > 0) {
    // Shift the buffer by one entry size
    bufferShift(&aInfo->frameInfoArray, sizeof(TDelayedFrameInfo));

    // Decrement the count of frames
    --aInfo->frameInfoArraySize;

    // Set result to zero, indicating successfull Pop.
    result = 0; 
  }

  assert(aInfo->frameInfoArraySize >= 0);

  return result;
}
//*****************************************************************************

const TDelayedFrameInfo *delayedLinkGetNextFrameInfoPtr(const TDelayedLinkInfo *aInfo)
{
  const TDelayedFrameInfo *fentry = NULL;

  // If array has elements > 0
  if (aInfo->frameInfoArraySize > 0) {
    // If our internal bookkeeping say we have frames, the data must not be 0
    assert(bufferGetLength(&aInfo->frameInfoArray) > 0);
    
    // Get & typecast an entry
    fentry = (TDelayedFrameInfo*)bufferGetPtr(&aInfo->frameInfoArray);

    // Print the entry
    delayedLinkPrintFrameInfoEntry(20, fentry);
  }
  
  return fentry;
}
//*****************************************************************************

int delayedLinkGetExpiredSize(const TDelayedLinkInfo *aInfo)
{
  int expired_size = 0;

  // If array has elements > 0
  if (aInfo->frameInfoArraySize > 0) {
    int i;
    long int exp_s, exp_us;

    // calculate the absolute time dl for entries
    ownTime_GetTimeOfDay(&exp_s, &exp_us);
    exp_s -= aInfo->linkDelayMs/1000;
    exp_us -= (aInfo->linkDelayMs%1000)*1000;
    if (exp_us < 0) {
      exp_s -= 1;
      exp_us += 1000000L;
    }
    
    // Get pointer to beginning of entrytable
    const TDelayedFrameInfo *fentry = 
      delayedLinkGetNextFrameInfoPtr(aInfo);

    // Now, iterate through the entrytable as long as expired entries are found.
    for (i=0; i < aInfo->frameInfoArraySize; ++i) {
      // Compare timestamp
      if (fentry->received_time_s < exp_s || 
	  (fentry->received_time_s==exp_s && fentry->received_time_us <= exp_us)) {
	// The entry is expired, increment the expired_size.
	expired_size += fentry->data_size;
	
      } else {
	dPrint(10,"Entry not expired. Breaking the loop. Expired size now %d bytes",
	       expired_size);
	// Entry was not expired. Newer packets cannot be older, so break here.
	break;

      }
      
      // Increment fentry pointer
      ++fentry;
    }
  }

  return expired_size;
}
//*****************************************************************************

int delayedLinkPopExpiredSize(TDelayedLinkInfo *aInfo, const int aSize)
{
  int result = 0;

  // If array has elements > 0
  if (aInfo->frameInfoArraySize > 0) {
    int i;
    int size_left = aSize;
    int shift_size = 0;

    // Get pointer to beginning of entrytable
    TDelayedFrameInfo *fentry = 
      (TDelayedFrameInfo *)delayedLinkGetNextFrameInfoPtr(aInfo);

    // Now, iterate through the entrytable as long as expired entries are found.
    for (i=0; i < aInfo->frameInfoArraySize && size_left > 0; ++i) {
      // check whether current entry is covered by the requested pop size
      if (fentry->data_size <= size_left) {
	// Current entry is covered. So remove the entry. (add its size to shift)
	shift_size += sizeof(TDelayedFrameInfo);

	// Decrement poppable size by data_size
	size_left -= fentry->data_size;

	// Increment the element counter (mostly for debugging purposes)
	++result;

      } else {
	// Size_left is smaller than next frames data_size, this is an error
	// in the handler!
	dPrint(4,"size_left is smaller than next frames data_size. Decrementing it! (%d < %d)",
	       size_left, fentry->data_size);
	fentry->data_size -= size_left;
	size_left = 0;
	break; // Optimize by one compare operation - break the loop.
      }
      
      // Increment fentry pointer
      ++fentry;
    }

    if (result > 0) {
      aInfo->frameInfoArraySize -= result;
      bufferShift(&aInfo->frameInfoArray, shift_size);
    }
  }

  return result;
}
//*****************************************************************************

int delayedLinkGetTimeMsUntilNextExpiredFrame(const TDelayedLinkInfo *aInfo)
{
  int time_until_expire = -1;
  
  // Get pointer to next frame (if any)
  const TDelayedFrameInfo *fentry = delayedLinkGetNextFrameInfoPtr(aInfo);
  
  if (fentry) {
    // We have frames in the buffer, so calculate the time to next.
    long int exp_s, exp_us;
    long int now_s, now_us;
    long int left_s, left_us;
    
    // When does the topmost frame expire?
    // (Add the 'timeout' the received timestamp)
    exp_s = fentry->received_time_s + aInfo->linkDelayMs/1000;
    exp_us = fentry->received_time_us + (aInfo->linkDelayMs%1000)*1000;
    if (exp_us > 1000000L) {
      exp_s += 1;
      exp_us -= 1000000;
    }
    //    dPrint(10,"Absolute DL for topmost frame = %d, %d", exp_s, exp_us);


    // Calculate the difference to current time
    // (substract 'now' from 'DL', if result is negative, the frame
    // is already expired)
    ownTime_GetTimeOfDay(&now_s, &now_us);
    left_s = exp_s - now_s;
    left_us = exp_us - now_us;
    if (left_us < 0) {
      left_s -= 1;
      left_us += 1000000L;
    }
    //    dPrint(6,"Time left until expired = %d, %d", left_s, left_us);


    // difference is now stored as seconds and microseconds -> convert to ms
    time_until_expire = (left_s * 1000) + (left_us / 1000);
    //    dPrint(1,"time_until_expire = %d", time_until_expire);

    
    // Now, check whether the result was negative (already expired)
    if (time_until_expire < 0) time_until_expire = 0;
  }

  return time_until_expire;
}
//*****************************************************************************

static void delayedLinkPrintFrameInfoEntry(const int level, 
					   const TDelayedFrameInfo *aFrameInfoEntry)
{
  dPrint(level,"Timestamp: %us, %uus, Size: %d bytes",
	 aFrameInfoEntry->received_time_s,
	 aFrameInfoEntry->received_time_us,
	 aFrameInfoEntry->data_size);
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
// gcc -g3 -lpthread -lrt -Wall -DLINUX_OS -DDELAYEDLINK_UNIT_TEST ownutils.c owndebug.c delayedlink.c buffer.c
// ./a.out
// valgrind -v ./a.out
//*****************************************************************************

#ifdef DELAYEDLINK_UNIT_TEST
int main(void)
{
  int i,es;

  setvbuf(stdout, NULL, _IONBF, 0);

  // Create new LinkInfo entry
  printf("Construct new LinkInfoEntry\n");
  TDelayedLinkInfo *li = delayedLinkCreateLinkInfoEntry(1000);
  assert(li != NULL);


  printf("Insert 100 frames, 50ms interval: ");
  // Insert 100 frames, sizes ranging from 1 - 1001 bytes
  for(i=0; i < 100; ++i) {
    assert(delayedLinkPushFrame(li, 1+10*i) == 0);
    ownSleep_ms(50);
    printf(".");
  }
  printf("done.\n");

  printf("Pop 100 frames: ");
  // Pop 100 frames
  for(i=0; i < 100; ++i) {
    assert(delayedLinkPopFrame(li) == 0);
    printf(".");
  }
  printf("done.\n");

  printf("Pop one extra frame..");
  // Pop 1 frame extra (must yield an error)
  assert(delayedLinkPopFrame(li) != 0);
  printf("Failed. (==ok, no entries in table)\n");

  printf("Push 3 frames, 100bytes each.. 333ms intervals.");
  // Push three frames.
  assert(delayedLinkPushFrame(li, 100) == 0);
  printf(".");
  ownSleep_ms(333);
  assert(delayedLinkPushFrame(li, 100) == 0);
  printf(".");
  ownSleep_ms(333);
  assert(delayedLinkPushFrame(li, 100) == 0);
  printf(".");
  ownSleep_ms(333);
  printf("done.\n");

  printf("Get Expired size...");
  es = delayedLinkGetExpiredSize(li);
  printf("Done. Got %d bytes as expired size\n", es);

  printf("Sleep 1000ms...");
  ownSleep_ms(1000);
  printf("done.\n");

  printf("Get Expired size...");
  es = delayedLinkGetExpiredSize(li);
  printf("Done. Got %d bytes as expired size\n", es);

  printf("Get Expired size and Pop it...");
  es = delayedLinkGetExpiredSize(li);
  delayedLinkPopExpiredSize(li, es);
  printf("Expired size was %d\n", es);
  
  printf("Get Expired size...");
  es = delayedLinkGetExpiredSize(li);
  printf("Done. Got %d bytes as expired size\n", es);

  // Pop until error (all)
  printf("Popping all elements left...");
  es = 0;
  while(delayedLinkPopFrame(li) == 0) { printf("."); es++; }
  printf("Popped %d entries\n", es);

  
  // Test partials
  printf("Testing partial pop...");
  assert(delayedLinkPushFrame(li, 100) == 0);
  printf("sleep_ms(1005)...");
  ownSleep_ms(1005);
  es = delayedLinkGetExpiredSize(li);
  printf("done. Expired size is %d.\n", es);
  printf("Popping Expired 90 bytes (100 bytes in Size really)...");
  delayedLinkPopExpiredSize(li, es-10);
  es = delayedLinkGetExpiredSize(li);
  printf("Read Expired size again, got %d bytes.", es);


  
  // Destroy linkinfo entry
  printf("Destroy LinkInfo entry..");
  delayedLinkDestroyLinkInfoEntry(li);
  printf("Done.\n\n\n");

  return 0;
}
#endif
//*****************************************************************************

